#include "StdAfx.h"

#include <malloc.h>
#ifdef WIN32
# include <windows.h>
#endif
#include <algorithm>

#include "TagStorage.h"
#include "Serialization.h"

#include "readtags.h"
#include "yasli/Files.h"
#include "yasli/EnumDescription.h"
#include "TagPreferences.h"
#include "yasli/Context.h"

SERIALIZATION_ENUM_BEGIN(TagType, "TagType")
SERIALIZATION_ENUM_VALUE(TAG_CLASS, "TAG_CLASS")
SERIALIZATION_ENUM_VALUE(TAG_ENUM, "TAG_ENUM")
SERIALIZATION_ENUM_VALUE(TAG_FUNCTION, "TAG_FUNCTION")
SERIALIZATION_ENUM_VALUE(TAG_TYPEDEF, "TAG_TYPEDEF")
SERIALIZATION_ENUM_VALUE(TAG_MEMBER, "TAG_MEMBER")
SERIALIZATION_ENUM_VALUE(TAG_ENUM_VALUE, "TAG_ENUM_VALUE")
SERIALIZATION_ENUM_VALUE(TAG_DEFINE, "TAG_DEFINE")
SERIALIZATION_ENUM_VALUE(TAG_UNKNOWN, "TAG_UNKNOWN")
SERIALIZATION_ENUM_END()


void Tag::serialize(Archive& ar)
{
	Context<TagBlock> node;

	ar(TagString(node(), name), "");
	ar(TagString(node(), nameFull), "");
	ar(TagString(node(), signature), "");
	ar(TagString(node(), title), "");
	ar(TagString(node(), titleFull), "");
	ar(location.line, "line");
	ar(TagString(node(), parentClass), "parent");
	ar(prototype, "prototype");
}


IMPLEMENT_CONTEXT(TagBlock)

bool serialize(Archive& ar, TagString& str, const char* name)
{
	if(ar.isOutput()){
		ASSERT(str.str_);
		std::string val = str.str_ ? str.str_ : "";
        return ar(val, name);
	}
	else{
		std::string val;
        bool result = ar(val, name);
		str.str_ = val.empty() ? "" : str.block_.addConstString(val.c_str());
		return result;
	}
}

// ---------------------------------------------------------------------------

TagStorage tagStorage;

TagStorage::TagStorage()
: cleared_(false)
{

}

void TagStorage::clear()
{
	flushTags();
	for(int i = 0; i < TAG_TYPE_COUNT; ++i)
		sortedTags_[i].clear();
	sources_.clear();
	cleared_ = true;
}

void TagStorage::addSource(TagSource* newSource)
{
	newSource->setStorage(this);
	sources_.push_back(newSource);
}


void TagStorage::sortedInsert(SortedTags& sortedTags, const Tag* tagToInsert)
{
	SortedTags::iterator it = std::lower_bound(sortedTags.begin(), sortedTags.end(), tagToInsert, TagCompare());
	sortedTags.insert(it, tagToInsert);
}

void TagStorage::addBlock(TagBlock* block)
{
    newBlocks_.push_back(block);
}

void TagStorage::addTagsByBlock(TagBlock* node)
{
	for(int i = 0; i < TAG_TYPE_COUNT; ++i){
		TagType type = (TagType)(i);
		SortedTags& sortedTags = sortedTags_[i];
		const TagBlock::Tags& tags = node->tags(type);
		TagBlock::Tags::const_iterator it;
		for(it = tags.begin(); it != tags.end(); ++it){
			const Tag* tag = &*it;
			sortedInsert(sortedTags, tag);
			sortedInsert(newTags_, tag);
		}
	}
}

int TagStorage::removeTagsByBlock(TagBlock* node)
{
	int count = 0;
	for(int i = 0; i < TAG_TYPE_COUNT; ++i){
		TagType type = (TagType)(i);
		SortedTags& sortedTags = sortedTags_[i];

		TagBlock::Tags::const_iterator it;
		const TagBlock::Tags& tags = node->tags(type);
		for(it = tags.begin(); it != tags.end(); ++it){
			const Tag* tag = &*it;

			SortedTags::iterator sit = std::find(sortedTags.begin(), sortedTags.end(), tag);
			if(sit != sortedTags.end()){
				sortedTags.erase(sit);
				++count;
				deadTags_.push_back(tag);
			}

			NewTags::iterator nit = std::find(newTags_.begin(), newTags_.end(), tag);
			if(nit != newTags_.end()){
				newTags_.erase(nit);
			}
		}		
	}
	return count;
}


void TagStorage::pickDeadBlocks(TagBlocks* blocks)
{
	TagBlocks::iterator it;
	for(it = blocks_.begin(); it != blocks_.end(); ){
		TagBlock* block = *it;
		if(block->dead()){
			blocks->push_back(block);
			it = blocks_.erase(it);
		}
		else{
			ASSERT(block->refCount() > 1);
			++it;
		}
	}
}

bool TagStorage::pollSources()
{
	Sources::iterator it;
	FOR_EACH(sources_, it){
		TagSource* source = *it;
		source->poll(this);
	}

	bool changed = false;

	TagBlocks deadBlocks;
	pickDeadBlocks(&deadBlocks);
	if(!deadBlocks.empty()){
		for(TagBlocks::iterator it = deadBlocks.begin(); it != deadBlocks.end(); ++it){
			TagBlock* block = *it;
			removeTagsByBlock(block);
		}
		deadBlocks.clear();
		changed = true;
	}


	if(!newBlocks_.empty()){
		for(TagBlocks::iterator it = newBlocks_.begin(); it != newBlocks_.end(); ++it){
			TagBlock* block = *it;
			addTagsByBlock(block);
			blocks_.push_back(block);
		}
		
		newBlocks_.clear();
		changed = true;
	}
	return changed;
}

void TagStorage::serialize(Archive& ar)
{
	if(ar.isInput()){
		for(int i = 0; i < TAG_TYPE_COUNT; ++i)
			sortedTags_[i].clear();
		flushTags();
	}
	cleared_ = true;
	ar(sources_, "sources");
	if(ar.isInput()){
		Sources::iterator it;
		for(it = sources_.begin(); it != sources_.end();){
			TagSource* source = *it;
			if(source){
				source->setStorage(this);
				source->reattach();
				++it;
			}
			else
				it = sources_.erase(it);
		}
	}
}


void updateTagsFile(const char* tagsFileName, const char* sourceFileName)
{
#ifdef WIN32
	std::string tagDirectory = Files::extractFilePath(tagsFileName);
	std::string commandLine = "ctags.exe --file-scope=yes --excmd=number --c++-kinds=+p --fields=afikmnSzts --sort=no -a ";
	commandLine += sourceFileName;

	PROCESS_INFORMATION processInfo;
	ZeroMemory(&processInfo, sizeof(processInfo));
	
	STARTUPINFO startupInfo;
	ZeroMemory(&startupInfo, sizeof(startupInfo));

	char* commandLineBuffer = (char*)alloca(commandLine.size() + 1);
	strcpy(commandLineBuffer, commandLine.c_str());
	CreateProcess(0, commandLineBuffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, tagDirectory.c_str(), &startupInfo, &processInfo);
	
	

	WaitForSingleObject(processInfo.hProcess, INFINITE);
	CloseHandle(processInfo.hProcess);
	CloseHandle(processInfo.hThread);
#else

#endif
}

// ---------------------------------------------------------------------------

void TagBlock::clear()
{
	for(int i = 0; i < TAG_TYPE_COUNT; ++i)
		tags_[i].clear();
}

const char* TagBlock::addConstString(const char* str)
{
	strings_.push_back(str);
	return strings_.back().c_str();
}

TagBlock::TagBlock()
: dead_(false)
{
}

TagBlock::~TagBlock()
{
}


const char* removeScope(const char* scopedName, const char* scopeSeparator)
{
	size_t sepLen = strlen(scopeSeparator);
	const char* name = scopedName;
	const char* p = scopedName;
	while(p = strstr(p, scopeSeparator)){
		p += sepLen;
		if(*p == '\0'){
			std::cout << "Bad scoped name: " << scopedName << std::endl;
			return scopedName;
		}
		else
			name = p;
	}
	return name;
}

bool TagBlock::loadFromFile(const char* tagFileName)
{
	clear();

	tagFileInfo info;
	tagEntry entry;
	tagFile* file = tagsOpen(tagFileName, &info);

	if(file == 0){
		return false;
		//RAISE(ErrorRuntime("Unable to tagsOpen..."));
	}

	int index = 0;
	while(tagsNext(file, &entry) == TagSuccess){
		Tag tag;
		tag.location.fileName = addConstString(entry.file);
		tag.location.line = entry.address.lineNumber;

		const char* shortName = removeScope(entry.name, "::");
		std::cout << entry.name << " => " << shortName << std::endl;
		if(shortName != entry.name){
			std::cout << "Skipping tag with name: " << entry.name << std::endl;
			continue;
		}

		tag.name = tag.nameFull = tag.title = tag.titleFull = addConstString(entry.name);
		switch(entry.kind[0]){
		case 'm':
		case 'v':
			tag.type = TAG_MEMBER;
			break;
		case 't':
			tag.type = TAG_TYPEDEF;
			break;
		case 's':
		case 'c':
			tag.type = TAG_CLASS;
			break;
		case 'd':
			tag.type = TAG_DEFINE;
			break;
		case 'f':
			tag.type = TAG_FUNCTION;
			tag.prototype = false;
			break;
		case 'p':
			tag.type = TAG_FUNCTION;
			tag.prototype = true;
			break;
		case 'e':
			tag.type = TAG_ENUM_VALUE;
			break;
		case 'g':
			tag.type = TAG_ENUM;
			break;
		default:
			tag.type = TAG_UNKNOWN;
			break;
		}
		//int index = tags_[tag.type].size();

		const char* namespace_name = 0;
		const char* class_name = 0;
		const char* enum_name = 0;
		const char* signature = 0;

		for(int i = 0; i < entry.fields.count; ++i){
			const char* key = entry.fields.list[i].key;
			const char* value = entry.fields.list[i].value;
			if(strcmp(key, "namespace") == 0){
				namespace_name = value;
			}
			else if(strcmp(key, "class") == 0 || strcmp(key, "struct") == 0){
				class_name = value;
			}
			else if(strcmp(key, "enum") == 0){
				enum_name = value;
			}
			else if(strcmp(key, "signature") == 0){
				signature = value;
				tag.signature = addConstString(signature);
			}
		}

		if(tag.type == TAG_CLASS || tag.type == TAG_ENUM && (namespace_name || class_name || enum_name)){
			std::string fullName;
			if(namespace_name){
				fullName += namespace_name;
				fullName += "::";
			}
			if(class_name){
				fullName += class_name;
				tag.parentClass = addConstString(fullName.c_str());
				fullName += "::";

			}
			if(enum_name){
				fullName += enum_name;
				tag.parentClass = addConstString(fullName.c_str());
				fullName += "::";
			}
			fullName += tag.name;
			tag.nameFull = addConstString(fullName.c_str());
		}
		else
			tag.nameFull = tag.name;
		
		// title
		if(tag.type != TAG_CLASS && tag.type != TAG_ENUM){
			if(signature){
				std::string title = tag.name;
				title += " ";
				title += signature;
				tag.title = addConstString(title.c_str());
			}
			else
				tag.title = tag.nameFull;

			if(signature || class_name || enum_name){
				std::string title = tag.nameFull;
				if(signature){
					title += " ";
					title += signature;
				}

				title += "  -  ";
				if(class_name){
					title += class_name;
					tag.parentClass = addConstString(class_name);
					ASSERT(enum_name == 0);
				}
				if(enum_name){
					ASSERT(class_name == 0);
					title += enum_name;
					tag.parentClass = addConstString(enum_name);
				}
				tag.titleFull = addConstString(title.c_str());
			}
			else{
				tag.titleFull = tag.title;
			}
		}
		else{
			tag.title = tag.name;
			tag.titleFull = tag.nameFull;
		}		

		// class
		tags_[tag.type].push_back(tag);
		++index;
	}
	tagsClose(file);
	return index != 0;
}


void TagBlock::setPath(const char* path)
{
	path_ = path;
}

void TagBlock::serialize(Archive& ar)
{
	Context<TagBlock>::push(this);
	EnumDescription& desc = getEnumDescription<TagType>();
	for(int i = 0; i < TAG_TYPE_COUNT; ++i){
		ar(tags_[i], desc.name(i));
		if(ar.isInput()){
			int count = int(tags_[i].size());
			const char* fileName = addConstString(path_.c_str());
			for(int j = 0; j < count; ++j){
				tags_[i][j].type = TagType(i);
				tags_[i][j].location.fileName = fileName;
			}
		}
	}
	Context<TagBlock>::pop();
}

// ---------------------------------------------------------------------------

void TagSource::serialize(Archive& ar)
{
	ar(path_, "path");
	//ar(root_, "root");
}
